/*
 *	2004 JAN 24 SHP
 *		1. Added licence and code comments
 */

using System;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.SqlTypes;

using FrameworkQ.ObjectSpace;
using FrameworkQ.ObjectSpace.Util;

namespace FrameworkQ.ObjectSpace.Data.Schema
{
	/// <summary>
	/// This object contains the schema information for a object type. 
	/// Also implements IPopulateObject
	/// </summary>
	public class ObjectSchema
	{

		private DatabaseConnection _dbConnection = null;
		private Type _type = null;
		private DataTable _SchemaTable = null;
		private PropertyMaps _propertyMaps;
		private PropertyMaps _propertyMapsByPropertyName;
        private bool _containsEncryptedProperties = false;


		/// <summary>
		/// Object schema constructor
		/// </summary>
		/// <param name="connectionString">Cnnection string to the sql server database</param>
		/// <param name="type">Type inoformation about the object to build schema on,</param>
		public ObjectSchema(Type type, DatabaseConnection dbConnection)
		{
			_dbConnection = dbConnection;
            _type = type;
			_SchemaTable = GetSchema();
			_propertyMaps = GetPropertyMappings (_SchemaTable,false);
			_propertyMapsByPropertyName = GetPropertyMappings (_SchemaTable,true);
		}

		/// <summary>
		/// schema table
		/// </summary>
		private DataTable SchemaTable
		{
			get
			{
				return _SchemaTable;
			}
		}

		/// <summary>
		/// Collection of property map objects for the type using column names as key
		/// </summary>
		public PropertyMaps Mapping
		{
			get
			{
				return _propertyMaps;
			}
		}

		/// <summary>
		/// Collection of property map objects for the type using property names as key
		/// </summary>
		public PropertyMaps MappingByProperty
		{
			get
			{
				return _propertyMapsByPropertyName;
			}
		}

		/// <summary>
		/// Type of object
		/// </summary>
		public Type ObjectType
		{
			get
			{
				return _type;
			}
		}

		/// <summary>
		///  Get the schema of the object
		/// </summary>
		/// <returns>A datatable representing the schema of the object</returns>
		private DataTable GetSchema()
		{
			// Get the table name. If there is not table map attribute 
			// then the object type name is the table name			
			this.TableName= _type.Name;
			Attribute[] typeAttributes = (Attribute[]) _type.GetCustomAttributes(typeof(TableMapAttribute),false);
			if (typeAttributes.Length>0)
			{
				TableMapAttribute mapAttr  = (TableMapAttribute)typeAttributes[0];
				this.TableName= mapAttr.Name;
			}
			string commandString = "SELECT * FROM [" + this.TableName + "]";
			IDbConnection connection = _dbConnection.Open();
			IDbCommand cmd = DBObjectsFactory.CreateCommand(_dbConnection.Provider, commandString,connection,_dbConnection.Transaction );
			
			IDataReader reader = cmd.ExecuteReader(CommandBehavior.SchemaOnly);
			DataTable dt = reader.GetSchemaTable();		
			_dbConnection.Close();
			return dt;			
		}

		private string _tableName;
		
		/// <summary>
		/// Name of the table to fetch data from
		/// </summary>
		public string TableName
		{
			get { return _tableName; }
			set { _tableName = value; }
		} 

		/// <summary>
		/// Returns a "table.x, table.y" formated string as an alternate for table.*
		/// </summary>
		/// <returns></returns>
		public string GetSelectAllString ()
		{
			StringBuilder sb = new StringBuilder ();
			foreach(PropertyMap map in this.Mapping.Values)
			{
				sb.AppendFormat("[{0}].[{1}] AS [{2}],", this.TableName, map.ColumnName, map.ColumnNameAlias);
			}
			sb.Remove (sb.Length-1,1);
			return sb.ToString();
		}

		public PropertyMaps GetPropertyMappings (DataTable dt, bool byPropertyName)
		{

			PropertyMaps maps = new PropertyMaps();
			Hashtable hsColumns = new Hashtable ();
			foreach (DataRow row in dt.Rows)
			{
				string columnName =(string) row ["ColumnName"];
				if (!hsColumns.ContainsKey(columnName))
					hsColumns.Add (columnName, row);
			}

			PropertyInfo[] props = _type.GetProperties(); 
			foreach (PropertyInfo propInfo in props )
			{
				bool isBound = true;
				PropertyBoundAttribute pb = ( PropertyBoundAttribute )CommonHelper.GetAttribute( propInfo , typeof( PropertyBoundAttribute ) );
				if( pb != null )
				{
					isBound = pb.IsBound;
				}
				if ( isBound ) 
				{
					string columnName = propInfo.Name ;
					bool primary = false;
					FieldMapAttribute fm = (FieldMapAttribute) CommonHelper.GetAttribute (propInfo, typeof(FieldMapAttribute));
					if (fm!=null)
					{
						columnName = fm.FieldName;
					}

					// Use primary key attribute only i fno primary key has been defined
					// otherwise use ID
					PrimaryKeyAttribute pm = (PrimaryKeyAttribute) CommonHelper.GetAttribute (propInfo, typeof(PrimaryKeyAttribute));
					if (pm!=null)
					{
						primary = true;					
					}
					else
					{
						if (propInfo.Name.Equals ("ID"))
						{
							if (maps.Primary==null)
							{
								primary = true;
							}
						}
					}

					if (hsColumns.ContainsKey(columnName))
					{
						DataRow row = (DataRow) hsColumns[columnName];
						PropertyMap pmap = new PropertyMap (propInfo, row);	
                        // TODO: DANGER
                        //pmap.ColumnOrdinal = (int)pmap.SchemaRow["ColumnOrdinal"];	
                        pmap.ColumnOrdinal = int.Parse(pmap.SchemaRow["ColumnOrdinal"].ToString());	
						pmap.ColumnName = columnName;
						pmap.ColumnNameAlias = string.Format("{0}.{1}", this.TableName,columnName);
                        try
                        {
                            pmap.IsIdentity = (bool)pmap.SchemaRow["IsIdentity"];
                        }
                        catch (Exception ex)
                        {
                            
                        }

                        EncryptedStringPropertyAttribute em = CommonHelper.GetAttribute(propInfo, typeof(EncryptedStringPropertyAttribute)) as EncryptedStringPropertyAttribute;
                        if (em != null)
                        {
                            _containsEncryptedProperties = true;
                            pmap.IsEncrypted = true;
                            pmap.UseEncryptionKeyFromConfig = em.LoadEncryptionKeyFromConfig;
                            pmap.EncrtyptionKey = em.EncryptionKey;
                        }
					
						if( !maps.Contains( propInfo.Name ) )
						{
							if (byPropertyName)
							{
								maps.Add (propInfo.Name,pmap);
							}
							else
							{
								maps.Add (columnName,pmap);
							}
					
							if (primary)
							{
								maps.Primary = pmap;
							}
						}
					}
				}
			}
			
			return maps;
		}

		/// <summary>
		/// Returns database type from schema row 
		/// </summary>
		/// <param name="row"></param>
		/// <returns></returns>
		internal static DbType GetDBTypeFromSchemaRow (DataRow row)
		{
			string typeName =  row["DataType"].ToString();
			return GetDBType(typeName);
		}
		internal static DbType GetDBType(string typeName)
		{
			switch (typeName)
			{
				case "System.String":
					return DbType.String;
				case "System.Int32" :
					return DbType.Int32;
				case "System.Decimal" :
					return DbType.Decimal ;
				case "System.Double" :
					return DbType.Double ;
				case "System.DateTime" :
					return DbType.DateTime;
				case "System.int64" :
					return DbType.Int64;
				case "System.Byte[]" :
					return DbType.Binary;
				case "System.Guid" :
					return DbType.Guid;
				case "System.Boolean":
					return DbType.Boolean;

				default:
					return DbType.String;
			}
		}
	}
}
